/*
 * Copyright 2019 xiaomaoguai.com All right reserved. This software is the
 * confidential and proprietary information of xiaomaoguai.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with xiaomaoguai.com.
 */

package com.xiaomaoguai.core.next.hsf.http;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URI;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import com.alibaba.fastjson.serializer.SerializerFeature;

import lombok.extern.slf4j.Slf4j;

/**
 * HSF接口使用HTTP请求代理类，真正发起HSF HTTP请求的地方
 *
 * @author chenyao
 * @since 2016年8月3日 下午5:15:43
 */
@Slf4j
public class HsfHttpRequestProxy implements InvocationHandler {
    private DefaultUriBuilderFactory uriTemplateHandler;
    private RestTemplate             restTemplate;
    private String                   serviceVersion;
    private String                   urlTemplate;
    private boolean                  responseInit = true;
    private ObjectDeserializer       responseBodyDeserializer;

    private HsfHttpRequestProxy() {
        uriTemplateHandler = new DefaultUriBuilderFactory();
    }

    /**
     * 创建实例对象
     *
     * @param restTemplate 访问Rest服务的客户端，建议此字段设置成具有连接池的单一对象
     * @param serviceVersion HSF接口的服务版本
     * @param serverUrl HSF服务的HTTP IP地址，举例：http://ip:port
     */
    public HsfHttpRequestProxy(RestTemplate restTemplate, String serviceVersion, String serverUrl) {
        this();
        this.restTemplate = restTemplate;
        this.serviceVersion = serviceVersion;
        this.urlTemplate = serverUrl + "/{0}:{1}/{2}";
    }

    /**
     * 创建实例对象
     *
     * @param restTemplate 访问Rest服务的客户端，建议此字段设置成具有连接池的单一对象
     * @param serviceVersion HSF接口的服务版本
     * @param serverUrl HSF服务的HTTP IP地址，举例：http://ip:port
     * @param responseBodyDeserializer
     *            使用自定义反序列化类来操作：HSF接口返回的值，解决某些泛型类型、特殊的需求（可选）
     */
    public HsfHttpRequestProxy(RestTemplate restTemplate, String serviceVersion, String serverUrl,
                               ObjectDeserializer responseBodyDeserializer) {
        this(restTemplate, serviceVersion, serverUrl);
        if (responseBodyDeserializer != null) {
            this.responseInit = false;
            this.responseBodyDeserializer = responseBodyDeserializer;
        }
    }

    /**
     * 执行HSF接口方法，使用HTTP请求
     *
     * @param proxy 代理接口类
     * @param method 当前执行的方法
     * @param args 方法的参数
     * @return 方法返回的结果
     * @throws Throwable 异常信息
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("toString".equals(methodName)) {
            return toString();
        }

        int size = args == null ? 0 : args.length;
        Object[] argsName = new Object[size];
        Object[] argsValue = new Object[size];
        if (size > 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < size; i++) {
                Class<?> parameterType = parameterTypes[i];
                argsName[i] = parameterType.getName();
                Object methodArg = args[i];
                if (methodArg == null) {
                    argsValue[i] = null;
                } else if (parameterType.equals(String.class)) {
                    // 对于String 类型的入参需要特殊处理
                    argsValue[i] = "\"" + methodArg.toString() + "\"";
                } else {
                    // 使用 SerializerFeature.WriteClassName 解决java bean泛型的问题
                    String argJson = JSON.toJSONString(methodArg, SerializerFeature.WriteClassName);
                    argsValue[i] = argJson;
                }
            }
        }

        // 构建请求参数
        JSONArray postData = new JSONArray(2);
        postData.add(argsName);
        postData.add(argsValue);
        String buffer = postData.toJSONString();

        String className = method.getDeclaringClass().getName();
        URI uri = uriTemplateHandler.expand(urlTemplate, className, serviceVersion, methodName);

        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
        HttpEntity<?> httpEntity = new HttpEntity<>(buffer, requestHeaders);
        Object requestBody = httpEntity.getBody();
        String responseBody = null;
        Type genericReturnType = method.getGenericReturnType();
        try {
            log.debug("requestUrl: {}, requestMethod: {}, requestBody: {}", uri, HttpMethod.POST, requestBody);
            ResponseEntity<String> responseEntity = restTemplate.exchange(uri, HttpMethod.POST, httpEntity,
                    String.class);
            responseBody = responseEntity.getBody();
            log.debug("requestUrl: {}, requestMethod: {}, responseBody: {}", uri, HttpMethod.POST, responseBody);

            // 反序列化结果
            if (!responseInit) {
                // 注册自定义反序列化实现类
                ParserConfig.getGlobalInstance().putDeserializer(genericReturnType, responseBodyDeserializer);
                responseInit = true;
            }
            return JSONObject.parseObject(responseBody, genericReturnType);
        } catch (Exception e) {
            if (e instanceof RestClientResponseException) {
                RestClientResponseException exception = (RestClientResponseException) e;
                // 打印出发送http请求的错误信息，帮助追踪错误源
                String exceptionBody = exception.getResponseBodyAsString();
                log.warn(
                        "requestUrl: {}, requestMethod: {}, requestBody: {}, responseBody: {}, genericReturnType: {}, exceptionBody: {}",
                        uri, HttpMethod.POST, requestBody, responseBody, JSON.toJSONString(genericReturnType),
                        exceptionBody);
            } else {
                log.warn("requestUrl: {}, requestMethod: {}, requestBody: {}, responseBody: {}, genericReturnType: {}",
                        uri, HttpMethod.POST, requestBody, responseBody, JSON.toJSONString(genericReturnType));
            }
            throw e;
        }
    }

    /**
     * 用于unit测试
     *
     * @return 访问Rest服务的客户端
     */
    public RestTemplate getRestTemplate() {
        return restTemplate;
    }
}
